eval("2**3 + 4*5")
28
Kaan Öztürk
June 8, 2018
Python dinamik tabir edilen dillerden biridir. Programda kullandığıız değişkenlerin tiplerini baştan bildirmeniz gerekmez. Program çalıştıkça işlenen komutlar, yeni nesneleri anında üretir. Bu dinamiklik sayesinde, dize olarak verilmiş Python komutlarını da işleyebilir, hatta program yazan programlar yazabiliriz.
Bu işlemi yapmak için iki Python fonksiyonu vardır: eval()
ve exec()
Dizinin bütün yazılarına erişmek için Python Programlamaya Giriş kategorimize bakabilirsiniz. Bu dizideki yazılar ayrıca Jupyter defterleri halinde GitHub depomuzda da mevcut.
Bu fonksiyon, Python komutları içeren bir dizeyi yorumlayıcıya gönderir ve sonucu geri verir.
Çalıştırma anında isim alanında bulunan değişkenler de kod dizesi içinde kullanılabilir.
Komut dizesi içindeki değişkeni başka bir değerle kullanmak isterseniz, değişkenleri bir sözlük ile verebilirsiniz.
Kullanıcıdan tek tek matematiksel ifadeler alıp sonucu yazan bir programcık yazalım. Kullanıcı “dur” yazdığında program sona ersin.
Nesnelerin yardım belgelerini daha önce kullanmıştık. Bir metodla ilgili bilgi almak için help()
komutunu kullandığımızda o metodun (bir fonksiyon nesnesidir) __doc__
isimli özelliği ekrana basılır. Buna doğrudan da erişebiliriz.
Diyelim bir nesne sınıfı altında tanımlanmış bütün metodların kısa tarifini (belge dizesini) ekrana dökmek istiyoruz. Ama bir sınıf altında, daha özel amaçlı, doğrudan kullanılmayan metodlar da bulunur. Bunların başlarında ve sonlarında bir çift altçizgi (“dunder”) bulunur.
Çift altçizgili metodları hariç tutarak, metodların kısa açıklamalarını listelemek için eval
’i bir döngü içinde kullanabiliriz.
L.append(object) -> None -- append object to end
L.clear() -> None -- remove all items from L
L.copy() -> list -- a shallow copy of L
L.count(value) -> integer -- return number of occurrences of value
L.extend(iterable) -> None -- extend list by appending elements from the iterable
L.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
L.insert(index, object) -- insert object before index
L.pop([index]) -> item -- remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
L.remove(value) -> None -- remove first occurrence of value.
Raises ValueError if the value is not present.
L.reverse() -- reverse *IN PLACE*
L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
Değer döndüren ifadeleri eval()
ile işletebiliriz, ama bir ifade (expression) olmayan, yani değer döndürmeyen komutları (söz gelişi, modül yükleme, fonksiyon tanımları, döngüler, atamalar vb.) çalıştırmak için exec()
fonksiyonuna ihtiyacımız var.
Örnek olarak, bir değişken ataması yapalım:
Bir formülde değişkene 0-9 arası değerler vererek bir tablo oluşturan bir kod yazalım.
değişken = "x"
formül = "1/(1+x) + x"
kod = """
for {0} in range(10):
print({0}, {1})""".format(değişken, formül)
print(kod)
for x in range(10):
print(x, 1/(1+x) + x)
0 1.0
1 1.5
2 2.3333333333333335
3 3.25
4 4.2
5 5.166666666666667
6 6.142857142857143
7 7.125
8 8.11111111111111
9 9.1
Farkedeceğiniz gibi, değişken
’in değeri "x"
olduğu için formülde de x
karakterini kullanmamız gerekiyor.
eval
/exec
fonksiyonları, işletilecek kodu barındıran dizenin yanı sıra iki parametre daha alırlar: globals ve locals. Bu parametreler özellikle belirtilmezse, eval
/exec
kodunda yorumlayıcının o andaki durumunda tanımlanmış olan bütün isimler kullanılabilir.
Yerel değişkenler bir fonksiyon içinden tanımlı olan, o fonksiyonun dışında tanınmayan isimlerdir. Global değişkenler ise bütün fonksiyonların erişebileceği değişkenlerdir. Yerel isimlere locals()
, global isimlere ise globals()
komutlarıyla ulaşılabilir. Bu komutlar değişken isimleriyle değerlerini eşleştiren birer sözlük döndürür.
eval
/exec
ile bir kod parçası çalıştırırken bu global ve yerel değişkenleri sınırlandırabiliriz. Bu fonksiyonların genel kullanımı şöyledir:
eval(source, globals=None, locals=None)
exec(source, globals=None, locals=None)
Burada globals
ve locals
parametreleri için global ve yerel değişkenleri tutan birer sözlük koyabiliriz.
{'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', 'eval("2**3 + 4*5")', 'x = 5\neval("2*x+4")', 'eval("2*x+4",{"x":10})', 'while(True):\n işlem = input("Bir işlem yazın: ")\n if işlem.strip().lower()=="dur": break\n print(eval(işlem))', 'list.append.__doc__', 'dir(list)', 'for metod in dir(list):\n if "__" not in metod:\n print(eval("list."+metod+".__doc__"))', 'exec("x=3.1415")\nx', 'değişken = "x" # Formülde kullanılacak değişken.\nformül = input("Bir matematiksel formül yazın: ")\nkod = """\nfor {0} in range(10):\n print({0}, {1})""".format(değişken, formül)\nprint(kod)', 'exec(kod)', 'globals()', 'def f(x):\n a = 10\n print(locals())\n\nf(3)', 'exec("print(locals())", None, {"abc": 17, "xyz": "Mehmet"})', 'exec("print(globals())", None, {"abc": 17, "xyz": "Mehmet"})'], '_oh': {1: 28, 2: 14, 3: 24, 5: 'L.append(object) -> None -- append object to end', 6: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'], 8: 3.1415, 11: {...}}, '_dh': ['/home/kaan/git/VeriDefteri/Python_Programlama'], 'In': ['', 'eval("2**3 + 4*5")', 'x = 5\neval("2*x+4")', 'eval("2*x+4",{"x":10})', 'while(True):\n işlem = input("Bir işlem yazın: ")\n if işlem.strip().lower()=="dur": break\n print(eval(işlem))', 'list.append.__doc__', 'dir(list)', 'for metod in dir(list):\n if "__" not in metod:\n print(eval("list."+metod+".__doc__"))', 'exec("x=3.1415")\nx', 'değişken = "x" # Formülde kullanılacak değişken.\nformül = input("Bir matematiksel formül yazın: ")\nkod = """\nfor {0} in range(10):\n print({0}, {1})""".format(değişken, formül)\nprint(kod)', 'exec(kod)', 'globals()', 'def f(x):\n a = 10\n print(locals())\n\nf(3)', 'exec("print(locals())", None, {"abc": 17, "xyz": "Mehmet"})', 'exec("print(globals())", None, {"abc": 17, "xyz": "Mehmet"})'], 'Out': {1: 28, 2: 14, 3: 24, 5: 'L.append(object) -> None -- append object to end', 6: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'], 8: 3.1415, 11: {...}}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f9d822f7b70>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f9d80260630>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f9d80260630>, '_': {...}, '__': 3.1415, '___': ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'], '_i': 'exec("print(locals())", None, {"abc": 17, "xyz": "Mehmet"})', '_ii': 'def f(x):\n a = 10\n print(locals())\n\nf(3)', '_iii': 'globals()', '_i1': 'eval("2**3 + 4*5")', '_1': 28, '_i2': 'x = 5\neval("2*x+4")', 'x': 9, '_2': 14, '_i3': 'eval("2*x+4",{"x":10})', '_3': 24, '_i4': 'while(True):\n işlem = input("Bir işlem yazın: ")\n if işlem.strip().lower()=="dur": break\n print(eval(işlem))', 'işlem': 'dur', '_i5': 'list.append.__doc__', '_5': 'L.append(object) -> None -- append object to end', '_i6': 'dir(list)', '_6': ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'], '_i7': 'for metod in dir(list):\n if "__" not in metod:\n print(eval("list."+metod+".__doc__"))', 'metod': 'sort', '_i8': 'exec("x=3.1415")\nx', '_8': 3.1415, '_i9': 'değişken = "x" # Formülde kullanılacak değişken.\nformül = input("Bir matematiksel formül yazın: ")\nkod = """\nfor {0} in range(10):\n print({0}, {1})""".format(değişken, formül)\nprint(kod)', 'değişken': 'x', 'formül': '2*x**3-10', 'kod': '\nfor x in range(10):\n print(x, 2*x**3-10)', '_i10': 'exec(kod)', '_i11': 'globals()', '_11': {...}, '_i12': 'def f(x):\n a = 10\n print(locals())\n\nf(3)', 'f': <function f at 0x7f9d80165d08>, '_i13': 'exec("print(locals())", None, {"abc": 17, "xyz": "Mehmet"})', '_i14': 'exec("print(globals())", None, {"abc": 17, "xyz": "Mehmet"})'}
globals yerine boş bir sözlük koyarsak, sadece Python’da tanımlanmış (built-in) isimlere erişilebilir sadece
{'__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <bound method Kernel.raw_input of <ipykernel.ipkernel.IPythonKernel object at 0x7f9d822f7f28>>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'Exception': <class 'Exception'>, 'TypeError': <class 'TypeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'GeneratorExit': <class 'GeneratorExit'>, 'SystemExit': <class 'SystemExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'ImportError': <class 'ImportError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'OSError': <class 'OSError'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'EOFError': <class 'EOFError'>, 'RuntimeError': <class 'RuntimeError'>, 'RecursionError': <class 'RecursionError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'AttributeError': <class 'AttributeError'>, 'SyntaxError': <class 'SyntaxError'>, 'IndentationError': <class 'IndentationError'>, 'TabError': <class 'TabError'>, 'LookupError': <class 'LookupError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ValueError': <class 'ValueError'>, 'UnicodeError': <class 'UnicodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'AssertionError': <class 'AssertionError'>, 'ArithmeticError': <class 'ArithmeticError'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'SystemError': <class 'SystemError'>, 'ReferenceError': <class 'ReferenceError'>, 'BufferError': <class 'BufferError'>, 'MemoryError': <class 'MemoryError'>, 'Warning': <class 'Warning'>, 'UserWarning': <class 'UserWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'BytesWarning': <class 'BytesWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'ConnectionError': <class 'ConnectionError'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'InterruptedError': <class 'InterruptedError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'open': <built-in function open>, 'copyright': Copyright (c) 2001-2017 Python Software Foundation.
All Rights Reserved.
Copyright (c) 2000 BeOpen.com.
All Rights Reserved.
Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.
Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object., '__IPYTHON__': True, 'display': <function display at 0x7f9d893481e0>, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f9d822f7b70>>}}
Bunlara bile erişimi kapatmamız mümkündür, aşağıdaki bölümde göreceğimiz gibi.
Dışarıdan alınan bir kodu çalıştırmak her zaman risklidir. exec()
ve eval()
fonksiyonlarının bilgisayarınıza bir kapı açtığını unutmayın.
Tehlikeyi örneklemek için, yukarıdaki örneği tekrar ele alalım. Siz bir formül beklerken, kötü niyetli bir kullanıcı işletim sisteminizi yönetecek bir komutu bu formülle beraber verebilir. Meselâ, formül sorulduğunda
'x); import os; os.system("touch hello.world");(0,
dizesinin verildiğini varsayalım.
Bu girdi sonucunda çalıştırılacak kod şöyle olur:
Burada kötü niyetli kullanıcı beklenen formülü verdikten sonra parantezi kapatmış ve işletim sistemine yönelik komutlar eklemiş. (Sondaki (0,
kısmı, kalıpta bulunan sağ parantezin sentaks hatası vermemesi için, onu etkisiz eleman haline dönüştürüyor.)
Bu kodu exec(kod)
ile çalıştırdığınızda ekrana sayılar tablosu çıkmasının yanı sıra, bu programı çalıştırdığınız dizinin altında hello.world isimli boş bir dosya yaratıldığını göreceksiniz (Linux kullanıyorsanız). Yani program işletim sisteminize erişebildi. Kötü niyetli bir saldırgan aynı yöntemle diskinizi silebilir, şifrelerinizi çalabilir, virüs yerleştirebilir.
Bu risklere karşı alınabilecek kısmi tedbirler vardır. En yaygın olanı, exec
’in çalıştığı sanal ortamdaki değişkenleri, globals ve locals parametreleri kullanarak düzenlemektir. Sözgelişi aşağıda, globals parametresi olarak {"__builtins__":None}
vermekle Python’un öntanımlı fonksiyonlarını kapatırız. Böylelikle import
ile bir modül yüklenmesini ve işletim sistemine ulaşılmasını engelleriz. Bu işlem range
ve print
fonksiyonlarını da kapatır, o yüzden locals parametresine bunların tanımlarını içeren bir sözlük veririz.
0 0
ImportError: __import__ not found
Bu yeni düzende import
fonksiyonu tanınmadığı için exec()
çağrısı bir hata verdi ve sızma engellendi. Aynı kodu beklenen şekilde bir girdiyle çalıştırdığınızda ise sorun yaşamazsınız.
Matematik kütüphanesindeki fonksiyonları kullanan işlemler yapmak istiyorsanız, gereken fonksiyonlardan oluşan bir “beyaz liste” oluşturabilirsiniz.
import math
exec(kod, {"__builtins__":None}, {"range":range, "print":print, "sqrt":math.sqrt, "log":math.log})
0 0.0
1 1.2872726592996706
2 2.4988211106473432
3 3.728009607357671
4 4.991893199734352
5 6.293943592339911
6 7.6340245917967176
7 9.010908615097694
8 10.423067565678043
9 11.868949934707404
Kendi kullanacağınız programlar veya bir masaüstü uygulaması için fazla endişe etmek gerekmeyebilir. Yanlış veya kötü niyetli bir kullanım sadece kullanıcıya zarar verecektir. Ama bir web uygulaması yazıyorsanız güvenliğe çok daha fazla dikkat etmelisiniz. Bu, daha derin bir uzmanlık gerektirir.
Özetle, dinamik olarak üretilen bir kodu işletmek için exec
/eval
kullanabilirsiniz. Bunun yararlı olduğu çeşitli durumlar vardır. Söz gelişi, Python sentaksıyla yazılmış bir dizeyi doğrudan alıp işletmek gibi.
exec
/eval
ilk bakışta çok hoş görünseler de çok sık kullanılmamalıdırlar, bazı sakıncaları vardır.
exec
/eval
fonksiyonlarının işe yaradığı durumlar vardır, ama probleminizi önce başka yaklaşımlarla çözmeye çalışın.